package com.excel.validators.core.extension.dyna;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.excel.validators.core.decorative.BasicClass;
import com.excel.validators.core.decorative.inner.IDynamicClass;
import com.excel.validators.core.extension.FactoryAnnotated;
import com.excel.validators.core.extension.ModelAnalysis;
import com.excel.validators.core.extension.annotation.AuditMetaData;
import com.excel.validators.core.extension.annotation.BaseNode;
import com.excel.validators.core.extension.annotation.DateNode;
import com.excel.validators.core.extension.annotation.DecimalNode;
import com.excel.validators.core.extension.annotation.FieldNode;
import com.excel.validators.core.extension.annotation.IngoreNode;
import com.excel.validators.core.extension.annotation.InnerModel;
import com.excel.validators.core.extension.annotation.InnerSummary;
import com.excel.validators.core.extension.annotation.NoneNode;

/**
 * @see FactoryAnnotated
 * @author wangxiaoliang10
 *
 * 2017年8月28日
 */
public class DynamicAnalysis implements IDynamicClass {

		private Map<String,ModelAnalysis> dynamicClasses;
		private Map<String,InnerModel> process;
		private int banner = -1;

		public DynamicAnalysis() {
				this.process = new LinkedHashMap<String,InnerModel>();
				this.dynamicClasses = new HashMap<String,ModelAnalysis>();
		}
		
		/**
		 * 初始化元数据
		 * @param namespace
		 * @return
		 */
		public ModelAnalysis createAnalysis(String namespace) {
				InnerModel excelModel = process.get(namespace);
				if (excelModel != null) {
						ClassLoader loader = this.getClass().getClassLoader();
						return new ModelAnalysis(loader, excelModel);
				}
				return null;
		}
		
		@Override
		public <T> void analysisElement(Class<T> basicClass) {
		  	// get annotation of @excelNode
		  	List<Annotation> fields = new LinkedList<Annotation>();
		  	// get all dclared fields with basic class
		  	Field[] dclaredfields = basicClass.getDeclaredFields();
		  	for (Field field : dclaredfields) {
		  			// if ingoreNode , this field will be ingore
		  			IngoreNode ingoreNode = field.getAnnotation(IngoreNode.class);
		  			if (ingoreNode != null) continue;
		  			// if field has annotation with @FieldNode
						FieldNode	fieldNode = createFieldNode(field);	
						// get class enum type
						String clsType = field.getType().getCanonicalName();
						int ctype = BasicClass.enumType(clsType);
						BaseNode baseNode = createBaseNode(ctype,field);
						AuditMetaData metadata = createAuditMetaData(field,fieldNode,baseNode);
						fields.add(metadata);
		  	}
		  	// create ExcelModel annotation
		  	InnerModel excelModel = createFields(basicClass,fields);
		  	process.put(basicClass.getSimpleName(), excelModel);
		  	resetBanner();
	  }

		
		
	  private void resetBanner() {
		  	banner = -1;
	  }

	  private InnerModel createFields(final Class basicClass, final List<Annotation> fields) {
		  	return new InnerModel() {
						@Override
						public Class<? extends Annotation> annotationType() {
								return null;
						}
						@Override
						public String namespace() {
								return basicClass.getSimpleName();
						}
						@Override
						public String clspath() {
								return basicClass.getCanonicalName();
						}
						@Override
						public AuditMetaData[] auditMetaDatas() {
								if (fields != null) {
										return fields.toArray(new AuditMetaData[fields.size()]);
								}
								return null;
						}
						@Override
						public String validator() {
								return "";
						}
		  	};
	  }

	  protected FieldNode createFieldNode(final Field field) {
			  // get field name from metedata
		  	final FieldNode fieldNode = field.getAnnotation(FieldNode.class);
				FieldNode result = 	new FieldNode() {
						@Override
						public Class<? extends Annotation> annotationType() {
								return null;
						}
						@Override
						public int index() {
								if (fieldNode != null) {
										int index = fieldNode.index();
										banner = index != -2 ? index - 1 : banner != -1 ? ++ banner : 0;
										return banner;
								}
								return ++ banner;
						}
						@Override
						public String columnName() {
								if (fieldNode != null) {
										String columnName = fieldNode.columnName();
										if (columnName.length() != 0) {
												return columnName;
										}
								}
								return field.getName();
						}
		  	};
				return result;
			}
	  
	  
	  private BaseNode createBaseNode(final int ctype, final Field field) {
			  // get field name from metedata
		  	if (ctype == BasicClass.DOUBLE
		  			|| ctype == BasicClass.FLOAT
		  			|| ctype == BasicClass.BIGDECIMAL) {
		  			final DecimalNode decimalNode = field.getAnnotation(DecimalNode.class);
		  			DecimalNode result = new DecimalNode() {
								@Override
								public Class<? extends Annotation> annotationType() {
										return null;
								}
								@Override
								public int decimal() {
										return decimalNode != null ? decimalNode.decimal() : 2;
								}
		  			};
		  			return createBaseNode(ctype,result);
		  	} else if (ctype == BasicClass.DATE) {
			  		final DateNode dateNode = field.getAnnotation(DateNode.class);
			  		DateNode result = new DateNode() {
								@Override
								public Class<? extends Annotation> annotationType() {
										return null;
								}
								@Override
								public String pattern() {
										return dateNode != null ? dateNode.pattern() : "yyyy-MM-dd HH:mm:ss";
								}
			  		};
			  		return createBaseNode(ctype,result);
		  	} else {
		  			final NoneNode noneNode = field.getAnnotation(NoneNode.class);
		  			return createBaseNode(ctype,noneNode);
		  	}
	  }

		private BaseNode createBaseNode(final int ctype,final Annotation annotation) {
				return new BaseNode() {
						@Override
						public Class<? extends Annotation> annotationType() {
								return null;
						}
						@Override
						public int type() {
								return ctype;
						}
						@Override
						public DecimalNode decimal() {
								if (annotation != null) {
										if (annotation instanceof DecimalNode) {
												return (DecimalNode) annotation;
										}
								}
								return null;
						}
						@Override
						public DateNode dateNode() {
								if (annotation != null) {
										if (annotation instanceof DateNode) {
												return (DateNode) annotation;
										}
								}
								return null;
						}
				};
		}
		
			private AuditMetaData createAuditMetaData(final Field field, final FieldNode fieldNode, final BaseNode baseNode) {
					AuditMetaData result = new AuditMetaData() {
							@Override
							public Class<? extends Annotation> annotationType() {
									return null;
							}
							@Override
							public String name() {
									return field.getName();
							}
							@Override
							public int index() {
									return fieldNode.index();
							}
							@Override
							public BaseNode base() {
									return baseNode;
							}
							@Override
							public String column() {
									return fieldNode.columnName();
							}
					};
					return result;
			}
			
			private InnerSummary createExcelSummary() {
					return new InnerSummary() {
							@Override
							public Class<? extends Annotation> annotationType() {
									return null;
							}
							@Override
							public InnerModel[] excelModels() {
									return process.values().toArray(new InnerModel[process.size()]);
							}
					};
			}
			
			@Override
			public String toString() {
					InnerSummary summary = createExcelSummary();
					InnerModel[] excelModels = summary.excelModels();
					StringBuilder builder = new StringBuilder("[");
					for (InnerModel excelModel : excelModels) {
							builder.append(" { namespace=" + excelModel.namespace());
							builder.append(", clspath=" + excelModel.clspath());
							builder.append(", validator=" + excelModel.validator());
							AuditMetaData[] metaDatas = excelModel.auditMetaDatas();
							builder.append(", AuditMetaData={");
							for (AuditMetaData metadata : metaDatas) {
									builder.append("[");
									int index = metadata.index();
									String name = metadata.name();
									builder.append("index=" + index);
									builder.append(", name=" + name);
									builder.append(", ");
									BaseNode base = metadata.base();
									builder.append("base={");
									int type = base.type();
									DateNode dateNode = base.dateNode();
									DecimalNode decimal = base.decimal();
									builder.append(" type=" + type);
									if (dateNode != null)
											builder.append(", dateNode=" + dateNode.pattern());
									if (decimal != null)
											builder.append(", decimal=" + decimal.decimal());
									builder.append("}], ");
							}
							builder.append("]}");
					}
					builder.append("}");
					return builder.toString();
			}
			
}
